import java.net._
import java.io._
import scala.io._
import scala.actors.Actor
import scala.actors.Actor._
import scala.collection.mutable
import scala.collection.mutable._
import scala.concurrent.ExecutionContext.Implicits.global
//import scala.actors.Futures._
import scala.concurrent._

object Server {
    def main(args: Array[String]) {
      val serverActor = new ServerActor
      serverActor.start
    }
  }

class ServerActor() extends Actor {
    def act() {
      println("Listens for new connections.")
      val listenSocket = new ServerSocket(6787)
      val actorArrayBufferHandlerActor = new ActorArrayBufferHandlerActor
      actorArrayBufferHandlerActor.start
      
      while(true) {
        val connectionSocket = listenSocket.accept()
        println("New connection created. Sending message for actorArrayBufferHandlerActor to handle it.")
        actorArrayBufferHandlerActor ! new NewConnection(connectionSocket)
      }
    }
  }
  
class ActorArrayBufferHandlerActor extends Actor { //maybe this can be converted to an object?
    def updateAndPushClientArrayBufferToClientServerActors(actorArrayBuffer: ArrayBuffer[(Int,Actor)]) {
      var clientArrayBufferDraft = new ArrayBuffer[Int]
      actorArrayBuffer.foreach(e => clientArrayBufferDraft += e._1)
      println("Pushing clientArrayBuffer to clients")
      val a = actorArrayBuffer.par; a.foreach(e => e._2 ! new clientArrayBufferClass(clientArrayBufferDraft)) //pushes the arrayBuffer to the actors. IN PRARALLEL. WOOOOOOOO!
      println("clientArrayBuffer has been pushed to clients")
    }
    def act() {
      var actorArrayBuffer = ArrayBuffer[(Int,Actor)]() //ATTENTION. Understanding that this is a sort of mutable list with tuples of type (int actorID, ref actor) is key to understanding the program
      var newActorID = -1
      var clientServerActor = new ClientServerActor(new java.net.Socket, newActorID, this) //dummy. To guarantee that there will not be a null error.
      loop {
        react {
          case NewConnection(clientSocket) =>
            newActorID+=1
            println("Socket and message received by ActorArrayBufferHandlerActor. Creating new ClientServerActor to handle the new connection.")
            var clientServerActor = new ClientServerActor(clientSocket,newActorID, this)
            actorArrayBuffer += ((newActorID,clientServerActor))
            actorArrayBuffer.last._2.start
            println("length of ActorArrayBuffer: " + actorArrayBuffer.length)
            updateAndPushClientArrayBufferToClientServerActors(actorArrayBuffer)
          case Remove(removalActorID) =>
            println("Message of removal received. Attempting removal from actorArrayBuffer. removalActorID is: " + removalActorID)
            actorArrayBuffer.foreach(e => if(e._1==removalActorID) {/*println("Found element for removal from actorArrayBuffer.") ;*/actorArrayBuffer -= e})
            updateAndPushClientArrayBufferToClientServerActors(actorArrayBuffer)
          case StringCarrier(messageString, clientServerActorID, actorIDTo) =>
            println("A message from client "+ clientServerActorID +" has been received by the server. About to route message to " + actorIDTo)
            actorArrayBuffer.foreach(e => if(e._1==actorIDTo){e._2 ! new StringCarrier(messageString, clientServerActorID, actorIDTo)})
            println("message routed to responsible ClientServerActor")
        }
      }
    }
  }
  
class ClientServerActor(clientSocket: Socket, clientServerActorID: Int, actorArrayBufferHandlerActor: Actor) extends Actor {
    def shutdown(clientSocket: Socket, objectIn: ObjectInputStream, objectOut: ObjectOutputStream, clientServerActorID: Int, actorArrayBufferHandlerActor: Actor) {
      objectOut.close()
      objectIn.close()
      clientSocket.close()
      actorArrayBufferHandlerActor ! new Remove(clientServerActorID)
    }
    
    def performRiskyNetworkFunctionWithNoReturn (riskyNetworkFunction: Unit, clientSocket: Socket, objectIn: ObjectInputStream, objectOut: ObjectOutputStream, clientServerActorID: Int, actorArrayBufferHandlerActor: Actor) {
      try {
        riskyNetworkFunction
      } catch {
        case _ : Throwable =>
          println("Exception thrown. Client probably disconnected")
          shutdown(clientSocket, objectIn, objectOut, clientServerActorID, actorArrayBufferHandlerActor)
      }
    }
    
    def performRiskyNetworkFunctionWithReturn (riskyNetworkFunction: Unit => Unit, clientSocket: Socket, objectIn: ObjectInputStream, objectOut: ObjectOutputStream, clientServerActorID: Int, actorArrayBufferHandlerActor: Actor) {
      
    }
  
    def act() {
      println("A ClientServerActor has received a connection. Handling it now.")
      val objectOut = new ObjectOutputStream(clientSocket.getOutputStream())
      val objectIn = new ObjectInputStream(clientSocket.getInputStream())
      
      performRiskyNetworkFunctionWithNoReturn(objectOut.writeObject(new StringCarrier("Hi, I am your serverActor on the server and you are client number " + clientServerActorID, clientServerActorID, clientServerActorID)), clientSocket, objectIn, objectOut, clientServerActorID, actorArrayBufferHandlerActor)
      
      future {
          while(true) {
            var messageFromClient = objectIn.readObject()
            messageFromClient match {
            case StringCarrier(messageString, actorIDFrom, actorIDTo) =>
              //println("has read message from client " + clientServerActorID + ", sending to client" + actorIDTo)
              actorArrayBufferHandlerActor ! new StringCarrier(messageString, clientServerActorID, actorIDTo)
              println("message successfully sent to client " + actorIDTo + " from client " + clientServerActorID)
            }
          }
      }
      
      
      loop {
        react {
          case clientArrayBufferClass(clientArrayBuffer) =>
            performRiskyNetworkFunctionWithNoReturn(objectOut.writeObject(new clientArrayBufferClass(clientArrayBuffer)), clientSocket, objectIn, objectOut, clientServerActorID, actorArrayBufferHandlerActor)
            println("has written clientArrayBuffer")
          case StringCarrier(messageString, clientServerActorID, actorIDTo) =>
            performRiskyNetworkFunctionWithNoReturn(objectOut.writeObject(new StringCarrier(messageString, clientServerActorID, actorIDTo)), clientSocket, objectIn, objectOut, clientServerActorID, actorArrayBufferHandlerActor)
        }
      }
      
    }
  }
  
case class Remove(actorID: Int)
case class NewConnection(clientSocket: Socket)
case class clientArrayBufferClass(clientArrayBuffer: ArrayBuffer[Int]) extends Serializable
case class StringCarrier(string: String, actorIDFrom: Int, actorIDTo: Int) extends Serializable